/*
 * JGrass - Free Open Source Java GIS http://www.jgrass.org
 * (C) {
 * HydroloGIS - www.hydrologis.com
 * C.U.D.A.M. - http://www.unitn.it/dipartimenti/cudam
 * The JGrass developer team - www.jgrass.org
 * }
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package eu.hydrologis.libs.newage;

import java.util.ArrayList;
import java.util.List;

import nl.alterra.openmi.sdk.backbone.ScalarSet;
import nl.alterra.openmi.sdk.backbone.SpatialReference;
import nl.alterra.openmi.sdk.backbone.TimeSpan;
import nl.alterra.openmi.sdk.backbone.TimeStamp;

import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.openmi.standard.IArgument;
import org.openmi.standard.IEvent;
import org.openmi.standard.IInputExchangeItem;
import org.openmi.standard.ILink;
import org.openmi.standard.ILinkableComponent;
import org.openmi.standard.IListener;
import org.openmi.standard.IOutputExchangeItem;
import org.openmi.standard.IQuantity;
import org.openmi.standard.ITime;
import org.openmi.standard.ITimeSpan;
import org.openmi.standard.ITimeStamp;
import org.openmi.standard.IValueSet;
import org.openmi.standard.IEvent.EventType;

import com.vividsolutions.jts.geom.Geometry;

import eu.hydrologis.libs.newage.swig.doubleArray;
import eu.hydrologis.libs.newage.swig.intArray;
import eu.hydrologis.libs.newage.swig.temp;
import eu.hydrologis.openmi.JGrassElementset;
import eu.hydrologis.openmi.JGrassIElementSet;
import eu.hydrologis.openmi.util.UtilitiesFacade;

/**
 * <h2>Model for temperature interpolation</h2>
 * <p>
 * This model interpolates temperature mesurements coming from measurement stations in points
 * defined from the output link.
 * </p>
 * <p>
 * NOTES:
 * </p>
 * <p>
 * The model has two links:
 * <ul>
 * <li>the input link that has to supply an elementset with the stations coordinates (elevation is
 * most important here)</li>
 * <li>the output link that has to supply an elementset with the interpolation coordinates
 * (elevation is most important here)</li>
 * </ul>
 * </p>
 * <p>
 * The model is openmi compliant
 * </p>
 * <p>
 * This component will call the native methods generated by SWIG in the files <code>temp.java</code>
 * and <code>tempJNI.java</code>
 * </p>
 * 
 * @author Andrea Antonello - www.hydrologis.com
 * @author Silvia Franceschi - www.hydrologis.com
 */
public class TemperatureInterpolator implements ILinkableComponent {

    private String quantityId;

    private String unitId;

    private String modelId;

    private String modelDescription;

    private IInputExchangeItem temperatureInputEI;

    private IOutputExchangeItem interpolatedTemperatureOutputEI;

    private ILink temperatureInputLink = null;

    private ILink interpolatedTemperatureOutputLink = null;

    private JGrassIElementSet temperatureElementSet;

    private JGrassIElementSet interpolatedTemperatureElementSet;

    private String gradient = null;

    private String mwt = null;

    private doubleArray quotastaArray;

    private doubleArray baricenterBasinsArray;

    private doubleArray tempArray;

    private int stationsNum;

    private int basinsNum;

    private doubleArray interpTempArray;

    private doubleArray paramsArray;

    private intArray numsArray;

    private JGrassIElementSet basinPositionElementSet;

    private IInputExchangeItem basinPositionInputEI;

    private ILink basinPositionInputLink;

    static {
        try {
            System.loadLibrary("temp");
        } catch (UnsatisfiedLinkError e) {
            System.out.println("Native code library failed to load.\n" + e);
        }
    }

    public void addLink( ILink link ) {
        if (link.getTargetComponent().equals(this)) {
            if (link.getTargetQuantity().equals(temperatureInputEI.getQuantity())
                    && link.getTargetElementSet().equals(temperatureInputEI.getElementSet())) {
                temperatureInputLink = link;
            } else if (link.getTargetQuantity().equals(basinPositionInputEI.getQuantity())
                    && link.getTargetElementSet().equals(basinPositionInputEI.getElementSet())) {
                basinPositionInputLink = link;
            }
        } else if (link.getSourceComponent().equals(this)) {
            if (link.getSourceQuantity().equals(interpolatedTemperatureOutputEI.getQuantity())
                    && link.getSourceElementSet().equals(
                            interpolatedTemperatureOutputEI.getElementSet())) {
                interpolatedTemperatureOutputLink = link;
            }
        } else {
            throw new RuntimeException("Wrong components");
        }

    }

    public void dispose() {
    }

    public void finish() {
    }

    public String getComponentDescription() {
        return "tempdescr";
    }

    public String getComponentID() {
        return "temp";
    }

    public ITimeStamp getEarliestInputTime() {
        return null;
    }

    public IInputExchangeItem getInputExchangeItem( int inputExchangeItemIndex ) {
        if (inputExchangeItemIndex == 0) {
            return temperatureInputEI;
        }
        if (inputExchangeItemIndex == 1) {
            return basinPositionInputEI;
        }
        return null;
    }

    public int getInputExchangeItemCount() {
        return 2;
    }

    public String getModelDescription() {
        return modelDescription;
    }

    public String getModelID() {
        return modelId;
    }

    public IOutputExchangeItem getOutputExchangeItem( int outputExchangeItemIndex ) {
        if (outputExchangeItemIndex == 0) {
            return interpolatedTemperatureOutputEI;
        }
        return null;
    }

    public int getOutputExchangeItemCount() {
        return 1;
    }

    public ITimeSpan getTimeHorizon() {
        ITimeStamp begin = new TimeStamp(15020.0);// start of 1900
        ITimeStamp end = new TimeStamp(782029.0);// start of 4000

        return new TimeSpan(begin, end);
    }

    public void initialize( IArgument[] properties ) {
        /*
         * arguments needed for the initialization
         */
        for( IArgument argument : properties ) {
            String key = argument.getKey();
            if (key.compareTo("quantityid") == 0) {
                quantityId = argument.getValue();
            }
            if (key.compareTo("unitid") == 0) {
                unitId = argument.getValue();
            }
            if (key.compareTo("gradient") == 0) {
                gradient = argument.getValue();
            }
            if (key.compareTo("mwt") == 0) {
                mwt = argument.getValue();
            }

        }
        modelId = "temperature interpolation";
        modelDescription = modelId;

        /*
         * temperature input exchange item
         */
        IQuantity temperatureQuantity = UtilitiesFacade.createScalarQuantity(quantityId, unitId);
        temperatureElementSet = new JGrassElementset("dummytemperatureelementset",
                "dummytemperatureelementset", JGrassIElementSet.JGrassElementType.PointCollection,
                new SpatialReference("some reference"));
        temperatureInputEI = UtilitiesFacade.createInputExchangeItem(this, temperatureQuantity,
                temperatureElementSet);

        /*
         * interpolated temperature output exchange item
         */
        IQuantity interpolatedRainfallQuantity = UtilitiesFacade.createScalarQuantity(quantityId,
                unitId);
        interpolatedTemperatureOutputEI = UtilitiesFacade.createOutputExchangeItem(this,
                interpolatedRainfallQuantity, temperatureElementSet);
        /*
         * basinPosition input exchange item
         */
        IQuantity basinPositionQuantity = UtilitiesFacade
                .createScalarQuantity("basinPosition", "m");
        basinPositionElementSet = new JGrassElementset("dummyareaelementset", "dummyareaelementset",
                JGrassIElementSet.JGrassElementType.PointCollection, new SpatialReference("some reference"));
        basinPositionInputEI = UtilitiesFacade.createInputExchangeItem(this, basinPositionQuantity,
                basinPositionElementSet);

    }

    public void prepare() {
        /*
         * at the point that the links are created and the components instantiated, we can retrieve
         * the elementsets from the source and target components.
         */
        temperatureElementSet = (JGrassIElementSet) temperatureInputLink.getSourceElementSet();
        /*
         * retrieve the positions from the right elementset
         */
        interpolatedTemperatureElementSet = (JGrassIElementSet) basinPositionInputLink.getSourceElementSet();
        // interpolatedTemperatureElementSet =
        // interpolatedTemperatureOutputLink.getTargetElementSet();

        stationsNum = temperatureElementSet.getElementCount();
        basinsNum = interpolatedTemperatureElementSet.getElementCount();
        quotastaArray = new doubleArray(stationsNum);
        baricenterBasinsArray = new doubleArray(basinsNum);
        // vector with point geometry objects
        FeatureCollection<SimpleFeatureType, SimpleFeature> featC = temperatureElementSet
                .getFeatureCollection();
        List<Geometry> inGeomVector = new ArrayList<Geometry>();
        FeatureIterator<SimpleFeature> fIterator = featC.features();
        while( fIterator.hasNext() ) {
            SimpleFeature f = fIterator.next();
            inGeomVector.add((Geometry) f.getDefaultGeometry());
        }
        featC.close(fIterator);
        for( int i = 0; i < stationsNum; i++ ) {
            quotastaArray.setitem(i, inGeomVector.get(i).getCoordinate().z);
        }

        featC = interpolatedTemperatureElementSet.getFeatureCollection();
        List<Geometry> outGeomVector = new ArrayList<Geometry>();
        fIterator = featC.features();
        while( fIterator.hasNext() ) {
            SimpleFeature f = fIterator.next();
            outGeomVector.add((Geometry) f.getDefaultGeometry());
        }
        featC.close(fIterator);
        for( int i = 0; i < basinsNum; i++ ) {
            baricenterBasinsArray.setitem(i, outGeomVector.get(i).getCoordinate().z);
        }

        tempArray = new doubleArray(stationsNum);
        interpTempArray = new doubleArray(basinsNum);
        paramsArray = new doubleArray(2);
        paramsArray.setitem(0, Double.parseDouble(gradient));
        paramsArray.setitem(1, Double.parseDouble(mwt));
        numsArray = new intArray(2);
        numsArray.setitem(0, stationsNum);
        numsArray.setitem(1, basinsNum);

    }

    public IValueSet getValues( ITime time, String linkID ) {
        /*
         * the pulling link is the output link (since here we have only one output link). Before we
         * use that, we need to get the temperature from the input exchange item
         */
        IValueSet temperatureValueSet = temperatureInputLink.getSourceComponent().getValues(time,
                temperatureInputLink.getID());
        int temperaturenum = ((ScalarSet) temperatureValueSet).size();
        if (temperaturenum != stationsNum)
            throw new RuntimeException(
                    "Stations number and temperature columns are not the same, but should!");

        /*
         * fill the temperatures array
         */
        for( int i = 0; i < stationsNum; i++ ) {
            tempArray.setitem(i, ((ScalarSet) temperatureValueSet).getScalar(i));
        }
        /*
         * print some debug info
         */
        // System.out.print("At time: " + time.toString() + " - ");
        // for( int i = 0; i < basinsNum; i++ ) {
        // System.out.print(baricenterBasinsArray.getitem(i) + "\t");
        // }
        // System.out.println();
        /*
         * do temperature interpolation
         */
        temp.cstige_temperature(paramsArray.cast(), numsArray.cast(), baricenterBasinsArray.cast(),
                quotastaArray.cast(), tempArray.cast(), interpTempArray.cast());

        double[] interpolatedTemperaturesArray = new double[basinsNum];
        for( int i = 0; i < interpolatedTemperaturesArray.length; i++ ) {
            interpolatedTemperaturesArray[i] = interpTempArray.getitem(i);
        }
        return new ScalarSet(interpolatedTemperaturesArray);
    }

    public void removeLink( String linkID ) {
        if (temperatureInputLink.getID().equals(linkID)) {
            temperatureInputLink = null;
        } else if (interpolatedTemperatureOutputLink.getID().equals(linkID)) {
            interpolatedTemperatureOutputLink = null;
        } else if (basinPositionInputLink.getID().equals(linkID)) {
            basinPositionInputLink = null;
        }
    }

    public String validate() {
        if (temperatureInputLink == null || interpolatedTemperatureOutputLink == null
                || basinPositionInputEI == null) {
            return "Not all required links have been connected";
        }
        return "";
    }

    public EventType getPublishedEventType( int providedEventTypeIndex ) {
        return null;
    }

    public int getPublishedEventTypeCount() {
        return 0;
    }

    public void sendEvent( IEvent Event ) {
    }

    public void subscribe( IListener listener, EventType eventType ) {
    }

public void unSubscribe( IListener arg0, EventType arg1 ) {
    
}

}
